package BLC

import (
	"encoding/hex"
	"errors"
	"fmt"
	"github.com/boltdb/bolt"
	"log"
	"math/big"
	"os"
	"strconv"
)

type BlockChain struct {
	DB  *bolt.DB //数据库
	Tip []byte   //最新区块的哈希
}

const dbName = "my.db"
const BlockDB = "MyBucket"

// 判断数据库是否存在
func dbExists() bool {
	if _, err := os.Stat(dbName); os.IsNotExist(err) {
		return false
	}
	return true
}

//初始化区块链
func CreateBlockChainWithGenesisBlock(addr string) *BlockChain {

	if dbExists() {
		fmt.Println("创世区块已经存在----")
		os.Exit(1)
	}

	db, err := bolt.Open(dbName, 0600, nil)
	if err != nil {
		log.Fatal(err)
	}

	var blockHash []byte
	var genesisBlock *Block
	err = db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(BlockDB))

		if b == nil {
			b, err = tx.CreateBucket([]byte(BlockDB))
			if err != nil {
				log.Println("创建数据库失败: ", err)
			}

		}

		//生成交易
		txCoinBase := NewCoinBaseTransaction(addr)
		//添加创世区块
		genesisBlock = CreateGenesisBlock([]*Transaction{txCoinBase})
		err = b.Put(genesisBlock.Hash, Serialize(genesisBlock))
		if err != nil {
			return err
		}
		err = b.Put([]byte("1"), genesisBlock.Hash)
		if err != nil {
			return err
		}
		blockHash = genesisBlock.Hash
		return nil
	})

	return &BlockChain{db, blockHash}
}

//添加新的区块
func (self *BlockChain) AddBlock(txs []*Transaction) {

	err := self.DB.Update(func(tx *bolt.Tx) error {

		db := tx.Bucket([]byte(BlockDB))
		if db == nil {
			log.Println("没有这个库名")
			return errors.New("没有这个库名")
		}
		//获取当前区块哈希值
		blockByte := db.Get(self.Tip)
		//反序列化当前区块
		block := DeserializeBlocker(blockByte)

		//创建新区块
		newBlock := NewBlock(block.Height+1, block.Hash, txs)

		//保存区块
		err := db.Put(newBlock.Hash, Serialize(newBlock))
		//更新最新区块的哈希
		err = db.Put([]byte("1"), newBlock.Hash)
		if err != nil {
			log.Println("存储数据失败")
		}

		self.Tip = newBlock.Hash
		//self.Block = newBlock
		return nil
	})

	if err != nil {
		log.Panicln("更新数据库数据失败: ", err)
	}

}

//遍历所有区块
func (self *BlockChain) PrintBlockChain() {
	fmt.Println("区块链的完整信息")
	iter := self.Iterator()

	for {
		fmt.Println("---------------分界-----------------")
		block := iter.Next()
		fmt.Printf("       Height : %d\n", block.Height)
		fmt.Printf("PrevBlockHash : %x\n", block.PrevBlockHash)
		fmt.Printf("         Hash : %x\n", block.Hash)
		fmt.Printf("    TimeStamp : %d\n", block.TimeStamp)
		fmt.Printf("  Transaction :\n")

		for _, tx := range block.TXs {
			fmt.Printf("    tx-hash: %x\n", tx.TxHash)
			fmt.Println("   输入: ")
			for _, in := range tx.VIns {
				fmt.Printf("              in_hash: %x\n", in.TxHash)
				fmt.Println("              in_VOut: ", in.VOut)
				fmt.Println("         in_ScriptDig: ", in.ScriptDig)
			}

			fmt.Println("   输出: ")
			for _, out := range tx.VOuts {
				fmt.Println("     out_ScriptPubKey: ", out.ScriptPubKey)
				fmt.Println("             out_VOut: ", out.Value)
			}
		}
		fmt.Printf("   Nonce : %d\n", block.Nonce)

		var hashInt big.Int
		hashInt.SetBytes(block.PrevBlockHash)

		if big.NewInt(0).Cmp(&hashInt) == 0 {
			fmt.Println("-----------遍历到创世区块------------")
			break // 跳出循环
		}
	}

}

func (self *BlockChain) CloseDB() {

	err := self.DB.Close()
	if err != nil {
		log.Println("数据库关闭失败 : ", err)
	}

}

func GetBlockChainObj() *BlockChain {

	db, err := bolt.Open(dbName, 0600, nil)
	if err != nil {
		log.Panicf("get the object blockChain failed:  %v\n", err)
	}
	var tip []byte
	err = db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(BlockDB))
		if b == nil {
			return errors.New("数据库里面是空的")
		}
		tip = b.Get([]byte("1"))
		return nil
	})
	if err != nil {
		log.Println("最新区块获取失败: ", err)
		return nil
	}

	return &BlockChain{DB: db, Tip: tip}

}

func (self *BlockChain) MineNewBlock(from, to, amount []string) {

	fmt.Println("原地址: ", from)
	fmt.Println("目标地址: ", to)
	fmt.Println("转账: ", amount)

	if len(from) != len(to) && len(from) != len(amount) {
		fmt.Println("可能少了一个参数")
		return
	}

	var txs []*Transaction

	for index, addr := range from {
		value, _ := strconv.Atoi(amount[index])
		tx := NewSimpleTransaction(addr, to[index], value, self, txs)
		txs = append(txs, tx)
	}

	var block *Block

	self.DB.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(BlockDB))

		hash := b.Get([]byte("1"))
		hashByte := b.Get(hash)
		block = DeserializeBlocker(hashByte)

		return nil
	})

	block = NewBlock(block.Height+1, block.Hash, txs)

	self.DB.Update(func(tx *bolt.Tx) error {

		b := tx.Bucket([]byte(BlockDB))
		if b != nil {
			err := b.Put(block.Hash, Serialize(block))
			if err != nil {
				log.Panicf("update the new block to db failed ! %v \n", err)
			}

			err = b.Put([]byte("1"), block.Hash)
			if err != nil {
				log.Panicf("update the new block  hash to db failed ! %v \n", err)
			}
			self.Tip = block.Hash
		}
		return nil
	})
}

var utxos []*UTXO
var spentTXOutput map[string][]int

func Search(addr string, txs []*Transaction) {

	for _, tx := range txs {
		if tx.VIns == nil {
			continue
		} else if !tx.IsCoinBaseTransaction() {
			//先查找输入
			for _, in := range tx.VIns {
				//判断身份
				if in.UnLockWithAddr(addr) {
					//查询用户的已经花费的交易
					strHash := hex.EncodeToString(in.TxHash)
					spentTXOutput[strHash] = append(spentTXOutput[strHash], in.VOut)
				}
			}
		}

		//查找缓存输出 与数据库输出

	work:
		// 遍历交易
		for index, vout := range tx.VOuts {
			if vout.UnLockScripPubKeyWithAddr(addr) {
				if len(spentTXOutput) > 0 {
					fmt.Println("-----------------1---------------", addr)
					var isUtxoTx bool
					for txHash, indexArray := range spentTXOutput {
						if hex.EncodeToString(tx.TxHash) == txHash {
							isUtxoTx = true
							//var isSpentUTXO bool
							for _, i := range indexArray {
								//判断是否已经花费
								if i == index {
									//已经交易的
									//isSpentUTXO = true
									continue work
								}
							}

							////未交易的
							//if isSpentUTXO == false {
							//	utxo := &UTXO{TxHash: tx.TxHash, Index: index, Outpot: vout}
							//	utxos = append(utxos, utxo)
							//}
						}
					}
					if isUtxoTx == false {
						utxo := &UTXO{TxHash: tx.TxHash, Index: index, Outpot: vout}
						utxos = append(utxos, utxo)
					}

				} else {
					fmt.Println("------------------2--------------", addr)
					utxo := &UTXO{TxHash: tx.TxHash, Index: index, Outpot: vout}
					utxos = append(utxos, utxo)
				}

			}
		}
	}
}

func (self *BlockChain) UTXOs(addr string, txs []*Transaction) []*UTXO {
	fmt.Println("the addr : ", addr)

	//存储已经花费的UTXO
	spentTXOutput = make(map[string][]int, 0)
	utxos = make([]*UTXO, 0)

	Search(addr, txs)

	// 遍历区块链  查询所以addr
	blockIter1 := self.Iterator()
	for {
		block := blockIter1.Next()
		if block == nil {
			log.Println("迭代器已经没有数据了")
			return nil
		}
		Search(addr, block.TXs)

		var hashInt big.Int
		hashInt.SetBytes(block.PrevBlockHash)
		if hashInt.Cmp(big.NewInt(0)) == 0 {
			break
		}
	}
	return utxos
}

func (self *BlockChain) getBalance(addr string) int64 {

	utxos := self.UTXOs(addr, []*Transaction{})
	var amount int64
	for _, utxo := range utxos {
		amount += utxo.Outpot.Value
	}
	return amount
}

//转账
//通过查询可用的UTXO , 超过需要的金额即可中断
func (self *BlockChain) FindSpendableUTXO(from string, amount int64, txs []*Transaction) (int64, map[string][]int) {

	spendableUTXO := make(map[string][]int)
	var value int64

	utxos := self.UTXOs(from, txs)

	for _, utxo := range utxos {
		value += utxo.Outpot.Value
		hash := hex.EncodeToString(utxo.TxHash)

		spendableUTXO[hash] = append(spendableUTXO[hash], utxo.Index)
		if value >= amount {
			break
		}
	}

	if value < amount {
		fmt.Println("余额不足", from)
		return 0, nil
	} else {
		return value, spendableUTXO
	}
}
